from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import uvicorn
import yaml
import os
import sys
import pandas as pd
import numpy as np
from typing import List, Dict, Any, Optional

# 导入自定义模块
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))

from src.models.model_trainer import HardwareModel, SoftwareModel, TicketModel
from src.data_prep.data_loader import process_hardware_features, process_software_features, process_ticket_features
from src.features.feature_engineer import HardwareFeatureEngineer, SoftwareFeatureEngineer, TicketFeatureEngineer, get_feature_importance


# 加载配置
with open('./config/config.yaml', 'r', encoding='utf-8') as f:
    config = yaml.safe_load(f)


# 初始化应用
app = FastAPI(
    title=config['project_name'],
    version=config['version'],
    description="物联网价签业务LightGBM服务API"
)

# 配置CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=config['api']['allowed_origins'],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


# 定义请求和响应模型
class HardwarePredictRequest(BaseModel):
    device_ids: List[str]
    predict_horizon: str = "7d"


class CriticalFeature(BaseModel):
    name: str
    value: float


class HardwarePrediction(BaseModel):
    device_id: str
    battery_failure_prob: float
    screen_failure_prob: float
    critical_features: List[CriticalFeature]


class HardwarePredictResponse(BaseModel):
    predictions: List[HardwarePrediction]


class SoftwareAnomalyRequest(BaseModel):
    device_id: str
    time_window: str = "24h"


class TriggerItem(BaseModel):
    error_code: Optional[str] = None
    metric: Optional[str] = None
    count: Optional[int] = None
    value: Optional[float] = None


class SoftwareAnomalyResponse(BaseModel):
    anomaly_score: float
    top_triggers: List[TriggerItem]
    recommended_action: str


class TicketClassifyRequest(BaseModel):
    ticket_id: str
    text_keywords: Dict[str, float]
    customer_segment: str
    device_age_bucket: str


class TicketClassification(BaseModel):
    problem_type: str
    confidence: float
    recommended_department: str


class TicketClassifyResponse(BaseModel):
    classification: TicketClassification


# 全局模型实例
models = {
    'hardware': None,
    'software': None,
    'ticket': None
}

# 特征工程实例
feature_engineers = {
    'hardware': HardwareFeatureEngineer(),
    'software': SoftwareFeatureEngineer(),
    'ticket': TicketFeatureEngineer()
}


# 加载模型
def load_models():
    """
    加载训练好的模型
    """
    # 加载硬件模型
    hardware_model = HardwareModel(config)
    hardware_model.load_models(
        config['models']['hardware_model_path'].replace('.txt', '_battery.txt'),
        config['models']['hardware_model_path'].replace('.txt', '_screen.txt')
    )
    models['hardware'] = hardware_model
    
    # 加载软件模型
    software_model = SoftwareModel(config)
    software_model.load(config['models']['software_model_path'])
    models['software'] = software_model
    
    # 加载工单模型
    ticket_model = TicketModel(config)
    ticket_model.load(config['models']['ticket_model_path'])
    models['ticket'] = ticket_model
    
    print("所有模型加载完成")


# 启动时加载模型
@app.on_event("startup")
async def startup_event():
    try:
        load_models()
    except Exception as e:
        print(f"模型加载失败: {e}")
        # 在开发环境，可以使用模拟模型
        print("使用模拟模型进行演示")


# 硬件诊断API
@app.post("/api/v1/hardware/predict", response_model=HardwarePredictResponse)
async def hardware_predict(request: HardwarePredictRequest):
    """
    硬件故障预测API
    """
    try:
        predictions = []
        
        for device_id in request.device_ids:
            # 这里应该从数据库或缓存获取设备数据
            # 为了演示，创建模拟数据
            device_data = create_mock_hardware_data(device_id)
            
            # 处理特征
            df = pd.DataFrame([device_data])
            df = process_hardware_features(df)
            df = feature_engineers['hardware'].build_features(df)
            
            # 获取用于预测的特征列（排除ID列）
            feature_cols = [col for col in df.columns if col != 'device_id']
            X = df[feature_cols]
            
            # 预测
            if models['hardware'] is None:
                # 模拟预测
                battery_prob = np.random.uniform(0.1, 0.9)
                screen_prob = np.random.uniform(0.05, 0.8)
            else:
                battery_prob = models['hardware'].battery_model.predict_proba(X)[0, 1]
                screen_prob = models['hardware'].screen_model.predict(X)[0]
            
            # 获取关键特征
            critical_features = get_critical_features(df, device_data, battery_prob)
            
            predictions.append(HardwarePrediction(
                device_id=device_id,
                battery_failure_prob=float(battery_prob),
                screen_failure_prob=float(screen_prob),
                critical_features=critical_features
            ))
        
        return HardwarePredictResponse(predictions=predictions)
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"预测失败: {str(e)}")


# 软件异常检测API
@app.post("/api/v1/software/anomaly", response_model=SoftwareAnomalyResponse)
async def software_anomaly(request: SoftwareAnomalyRequest):
    """
    软件异常检测API
    """
    try:
        # 创建模拟数据
        software_data = create_mock_software_data(request.device_id)
        
        # 处理特征
        df = pd.DataFrame([software_data])
        df = process_software_features(df)
        df = feature_engineers['software'].build_features(df)
        
        # 获取用于预测的特征列
        feature_cols = [col for col in df.columns if col != 'device_id']
        X = df[feature_cols]
        
        # 预测异常分数
        if models['software'] is None:
            # 模拟异常分数
            anomaly_score = np.random.uniform(0.3, 0.95)
        else:
            anomaly_score = models['software'].get_anomaly_score(X)[0]
        
        # 获取触发因素
        top_triggers = get_top_triggers(software_data, anomaly_score)
        
        # 生成推荐操作
        recommended_action = generate_recommended_action(top_triggers)
        
        return SoftwareAnomalyResponse(
            anomaly_score=float(anomaly_score),
            top_triggers=top_triggers,
            recommended_action=recommended_action
        )
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"异常检测失败: {str(e)}")


# 工单分类API
@app.post("/api/v1/ticket/classify", response_model=TicketClassifyResponse)
async def ticket_classify(request: TicketClassifyRequest):
    """
    工单分类API
    """
    try:
        # 创建工单数据
        ticket_data = {
            'ticket_id': request.ticket_id,
            'text_keywords': request.text_keywords,
            'customer_segment': request.customer_segment,
            'device_age_bucket': request.device_age_bucket
        }
        
        # 处理特征
        df = pd.DataFrame([ticket_data])
        df = process_ticket_features(df)
        df = feature_engineers['ticket'].build_features(df)
        
        # 获取用于预测的特征列
        feature_cols = [col for col in df.columns if col != 'ticket_id']
        X = df[feature_cols]
        
        # 预测分类
        if models['ticket'] is None:
            # 模拟分类
            problem_types = ['硬件_屏幕', '硬件_电池', '软件_网络', '软件_系统', '其他']
            problem_type = np.random.choice(problem_types)
            confidence = np.random.uniform(0.7, 0.98)
        else:
            prob_df = models['ticket'].predict_proba(X)
            problem_type = prob_df.idxmax(axis=1)[0]
            confidence = prob_df.max(axis=1)[0]
        
        # 确定推荐部门
        recommended_department = get_recommended_department(problem_type)
        
        return TicketClassifyResponse(
            classification=TicketClassification(
                problem_type=problem_type,
                confidence=float(confidence),
                recommended_department=recommended_department
            )
        )
        
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"工单分类失败: {str(e)}")


# 辅助函数
def create_mock_hardware_data(device_id: str) -> Dict[str, Any]:
    """
    创建模拟硬件数据
    """
    import random
    return {
        'device_id': device_id,
        'days_in_use': random.randint(10, 365),
        'avg_voltage': round(random.uniform(2.8, 3.6), 2),
        'voltage_drop_rate': round(random.uniform(0.001, 0.05), 3),
        'signal_strength_avg': round(random.uniform(-100, -70), 2),
        'screen_refresh_count': random.randint(500, 5000),
        'temp_max': round(random.uniform(30, 50), 2),
        'temp_min': round(random.uniform(-20, 20), 2),
        'temp_var': round(random.uniform(5, 25), 2),
        'firmware_version': random.choice(['V1.2.3', 'V1.3.0', 'V2.0.1']),
        'location_cluster': random.choice(['A12', 'B05', 'C08'])
    }


def create_mock_software_data(device_id: str) -> Dict[str, Any]:
    """
    创建模拟软件数据
    """
    import random
    return {
        'device_id': device_id,
        'error_code_dist': {
            'NET_TIMEOUT': random.randint(0, 50),
            'MEM_OVERFLOW': random.randint(0, 10),
            'IO_ERROR': random.randint(0, 20),
            'SENSOR_FAIL': random.randint(0, 15)
        },
        'heartbeat_interval_std': round(random.uniform(1.0, 10.0), 2),
        'reboot_count': random.randint(0, 10),
        'network_retry_ratio': round(random.uniform(0.01, 0.8), 3),
        'log_entropy': round(random.uniform(1.0, 3.0), 2)
    }


def get_critical_features(df: pd.DataFrame, device_data: Dict[str, Any], battery_prob: float) -> List[CriticalFeature]:
    """
    获取关键特征
    """
    critical_features = []
    
    # 根据电池概率添加关键特征
    if battery_prob > 0.7:
        critical_features.append(CriticalFeature(
            name='voltage_drop_rate',
            value=float(device_data['voltage_drop_rate'])
        ))
        critical_features.append(CriticalFeature(
            name='temperature_var',
            value=float(device_data['temp_var'])
        ))
    elif battery_prob > 0.4:
        critical_features.append(CriticalFeature(
            name='avg_voltage',
            value=float(device_data['avg_voltage'])
        ))
    
    return critical_features


def get_top_triggers(software_data: Dict[str, Any], anomaly_score: float) -> List[TriggerItem]:
    """
    获取异常触发因素
    """
    triggers = []
    
    # 检查错误码
    error_dist = software_data.get('error_code_dist', {})
    if error_dist:
        top_error = max(error_dist.items(), key=lambda x: x[1])
        triggers.append(TriggerItem(
            error_code=top_error[0],
            count=top_error[1]
        ))
    
    # 检查网络问题
    if software_data['network_retry_ratio'] > 0.3:
        triggers.append(TriggerItem(
            metric='network_retry_ratio',
            value=float(software_data['network_retry_ratio'])
        ))
    
    # 检查心跳间隔
    if software_data['heartbeat_interval_std'] > 5.0:
        triggers.append(TriggerItem(
            metric='heartbeat_interval_std',
            value=float(software_data['heartbeat_interval_std'])
        ))
    
    return triggers[:2]  # 最多返回2个触发因素


def generate_recommended_action(triggers: List[TriggerItem]) -> str:
    """
    生成推荐操作
    """
    if any(t.error_code == 'NET_TIMEOUT' for t in triggers):
        return "检查网络配置或升级固件"
    elif any(t.metric == 'heartbeat_interval_std' for t in triggers):
        return "检查设备连接稳定性，可能需要重启设备"
    else:
        return "建议技术支持人员进一步检查设备日志"


def get_recommended_department(problem_type: str) -> str:
    """
    根据问题类型获取推荐部门
    """
    if problem_type.startswith('硬件'):
        return '硬件技术支持'
    elif problem_type.startswith('软件'):
        return '软件开发团队'
    else:
        return '客户服务中心'


# 根路径
@app.get("/")
async def root():
    return {
        "message": "物联网价签业务LightGBM服务API",
        "version": config['version'],
        "docs": "/docs"
    }


# 健康检查
@app.get("/health")
async def health_check():
    return {
        "status": "healthy",
        "models_loaded": all(m is not None for m in models.values())
    }


if __name__ == "__main__":
    uvicorn.run(
        "server:app",
        host=config['api']['host'],
        port=config['api']['port'],
        reload=config['api']['debug']
    )
